﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;
using Framework.Interfaces.Helpers;
using RSSNewsGrabberModule.Interfaces;

namespace RSSNewsGrabberModule.Mapping
{
    public static class MappingLoader
    {
        public static FieldsMappingCollection Load(string mappingName)
        {
            XDocument mapping = XDocument.Load(Environment.CurrentDirectory + mappingName);
            return CreateMappingStructure(mapping);
        }

        //create mapping structure
        // key - resource url
        // value - configuration object for this url
        // if key default - default conf object
        private static FieldsMappingCollection CreateMappingStructure(XDocument document)
        {
            if (document == null) throw new ArgumentNullException("document");
            var root = document.Root;
            if (root == null) throw new NullReferenceException(string.Format("Invalid xml configuration file for field mapping"));
            var mainMapping = new FieldsMappingCollection();
            foreach (var xElement in root.Elements("rssChannel"))
            {
                var key = xElement.Attribute("url");
                if (key == null) throw new NullReferenceException("Expected url attribute in teg rssChannel");
                var mappping = CreateMappingElement(xElement);
                mainMapping.Mappings.Add(new KeyValuePair<string, FieldsMapping>(key.Value, mappping));
            }

            return mainMapping;
        }

        private static FieldsMapping CreateMappingElement(XElement element)
        {
            if (element == null) throw new ArgumentNullException("element");
            
            try
            {
                var rootList = GetRootsList(element.Element("rootsLevels"));
                var header = element.Element("header");
                var item = element.Element("item");
                string headerName = element.With(x => x.Attribute("xmlNodeName")).With(x => x.Value);
                string itemName = element.With(x => x.Attribute("xmlNodeName")).With(x => x.Value);
                var newFieldsMapping = new FieldsMapping(rootList, headerName, itemName);
                foreach (var xEl in header.With(x=>x.Elements("add")))
                {
                    var fieldInfo = new FieldInfo(xEl.Attribute("fieldType").Value,
                                                  xEl.Attribute("fieldName").Value,
                                                  GetConverter(xEl));
                    newFieldsMapping.
                        Header.Add(new KeyValuePair<string, FieldInfo>(xEl.Attribute("xmlNodeName").Value, fieldInfo));
                }
                foreach (var xEl in item.Elements("add"))
                {
                    var fieldInfo = new FieldInfo(xEl.Attribute("fieldType").Value,
                                                  xEl.Attribute("fieldName").Value,
                                                  GetConverter(xEl));
                    newFieldsMapping.
                        Item.Add(new KeyValuePair<string, FieldInfo>(xEl.Attribute("xmlNodeName").Value, fieldInfo));
                }
                return newFieldsMapping;
            }
            catch (NullReferenceException e)
            {
                throw new NullReferenceException(string.Format("Invalid fields mapping configuration element : {0}",element.Name),e);
            }
            
        }

        private static Assembly GetAssembly(string name)
        {
            AppDomain currentDomain = AppDomain.CurrentDomain;
            Assembly[] assemblies = currentDomain.GetAssemblies();
            Assembly assembly = assemblies.SingleOrDefault(assm => assm.FullName.Equals(name)) ?? Assembly.Load(name);
            return assembly;
        }

        //get converter type
        private static IConverter GetConverter(XElement element)
        {
            var converterElem = element.Attribute("converter");
            if (converterElem == null)
            {
                var type = typeof (IConverter);
                var attributeCollection = type.GetCustomAttributes(typeof (DefaultImplementationAttribute),false);
                var attribute = attributeCollection.FirstOrDefault();
                if (attribute == null)
                    throw new NullReferenceException("Default implementation for IConverter interface don't set");
                return Activator.CreateInstance(((DefaultImplementationAttribute) attribute).ClassType) as IConverter;
            }
            var arrSt =  converterElem.Value.Split(',');
            if (arrSt.Count() != 2) 
                throw new ConfigurationErrorsException(string.Format("Invalid accembly {0} define in configuration file", converterElem.Value));
            var converterType = arrSt[0];
            var assemblyName = arrSt[1];
            Assembly asm = GetAssembly(assemblyName);
            var insType = asm.GetType(converterType);
            return Activator.CreateInstance(insType) as IConverter;
        }

        //add root nodes in list
        //<abc><ss></ss></abc> --> {"abc"}{"ss"}
        private static LinkedList<string> GetRootsList(XElement element)
        {
            var elem = element;
            var listResult = new LinkedList<string>();
            while (elem.Element("root")!=null)
            {
                elem = elem.Element("root");
                listResult.AddLast(elem.Value);
            }
            return listResult;
        }
    }
}
